﻿using System;
using System.Runtime.InteropServices;

namespace IupNET
{
    /// <summary>
    /// This is a 2d affine transfomation matrix
    /// </summary>
    public class Matrix2d
    {
        /// <summary>
        /// The elements of the matrix:
        /// 0: xaxis x
        /// 1: xaxis y
        /// 2: yaxis x
        /// 3: yaxis y
        /// 4: translate x
        /// 5: translate y
        /// </summary>
        public readonly double[] M = new double[6];

        public static readonly Matrix2d Identity = new Matrix2d();

        public Matrix2d()
        {
            //init to identity matrix
            M[0] = 1.0;
            M[3] = 1.0;
        }

        public Matrix2d(IntPtr doubles)
        {
            Marshal.Copy(doubles, M, 0, 6);
        }

        public Matrix2d(double xaxis_x, double xaxis_y, double yaxis_x, double yaxis_y, double transx, double transy)
        {
            M[0] = xaxis_x;
            M[1] = xaxis_y;
            M[2] = yaxis_x;
            M[3] = yaxis_y;
            M[4] = transx;
            M[5] = transy;
        }

        public static Matrix2d Translate(double dx,double dy) {
            Matrix2d m=new Matrix2d();
            m.M[4]=dx;
            m.M[5]=dy;
            return m;
        }

        public void SetIdentity()
        {
            M[0] = 1.0;
            M[1] = 0.0;
            M[2] = 0.0;
            M[3] = 1.0;
            M[4] = 0.0;
            M[5] = 0.0;
        }

        /// <summary>
        /// Multiplies a matrix with another.
        /// </summary>
        public static Matrix2d operator *(Matrix2d a, Matrix2d b)
        {
            return new Matrix2d(
                /*a.M[0] * b.M[0] + a.M[2] * b.M[1],
                a.M[1] * b.M[0] + a.M[3] * b.M[1],

                a.M[0] * b.M[2] + a.M[2] * b.M[3],
                a.M[1] * b.M[2] + a.M[3] * b.M[3],

                a.M[0] * b.M[4] + a.M[2] * b.M[5] + a.M[4],
                a.M[1] * b.M[4] + a.M[3] * b.M[5] + a.M[5]*/

                //The old multipliction above is stored here in case we want to to a forward concatenation in the future
                //the one below makes a 'pre-concatenation', which is what we normally want

                b.M[0] * a.M[0] + b.M[2] * a.M[1],
                b.M[1] * a.M[0] + b.M[3] * a.M[1],

                b.M[0] * a.M[2] + b.M[2] * a.M[3],
                b.M[1] * a.M[2] + b.M[3] * a.M[3],

                b.M[0] * a.M[4] + b.M[2] * a.M[5] + b.M[4],
                b.M[1] * a.M[4] + b.M[3] * a.M[5] + b.M[5]
            );
        }

        /// <summary>
        /// Computes a new matrix which is a pure rotation matrix.
        /// </summary>
        public static Matrix2d Rotate(double angle)
        {
            double sina = Math.Sin(angle);
            double cosa = Math.Cos(angle);

            return new Matrix2d(
                cosa, sina,
                -sina, cosa,
                0.0, 0.0
            );
        }

        /// <summary>
        /// Computes a new matrix which is a pure scaling matrix where x and y scale is the same.
        /// </summary>
        public static Matrix2d Scale(double scale)
        {
            return new Matrix2d(
                scale, 0.0,
                0.0, scale,
                0.0, 0.0
            );
        }

        /// <summary>
        /// Computes a new matrix which is a 'stretch' matrix,that is scaling in x and y might be diffrent.
        /// </summary>
        public static Matrix2d Stretch(double xfactor, double yfactor)
        {
            return new Matrix2d(
                xfactor, 0.0,
                0.0, yfactor,
                0.0, 0.0
                );
        }


        public double Determinant {
            get {
                return M[0]*M[3]-M[1]*M[2];
            }
        }

        public Matrix2d Inverse {
            get
            {
                double d = Determinant;
                if (d == 0.0)
                    throw new Exception("Trying to invert non-invertable matrix");
                return new Matrix2d(
                    M[3] / d,
                    -M[1] / d,
                    -M[2] / d,
                    M[0] / d,
                    (M[2] * M[5] - M[3] * M[4]) / d,
                    (M[1] * M[4] - M[0] * M[5]) / d
                  );
            }
        }

    }
}
